AppSyncのログ・WAF・X-Ray設定をCDKで行ってみる
吉川です。
早速ですが、AppSyncを使う上でよく必要になると思われる設定をCDKで行ってみたので紹介します。
- ログをCloudWatch Logsに出力
- AWS WAFの設定と紐付け
- X-Ray設定
上記を行っていきます。
初期コード
下記のStack定義CDKコードを用意します。Lambda関数をDataSourceとするシンプルなAppSyncAPIを定義しました。
import { Construct } from 'constructs' import * as cdk from 'aws-cdk-lib' import * as appsync from '@aws-cdk/aws-appsync-alpha' import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs' export class AppSyncStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props) // AppSyncAPIを作成 const api = new appsync.GraphqlApi(this, 'myAppsyncApi', { name: 'myAppsyncApi', schema: appsync.Schema.fromAsset('./schema.graphql'), authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY, }, }, }) // Lambda関数を作成 const findUserFn = new lambdaNodejs.NodejsFunction(this, 'findUserFn', { entry: 'lambda-handler/find-user-handler.ts', }) // Lambda関数をDataSourceとしてAppSyncAPIと紐付ける const findUserDs = api.addLambdaDataSource('findUserDs', findUserFn) // schema.graphqlで定義した中のどの操作とマッピングするかを指定 findUserDs.createResolver({ typeName: 'Query', fieldName: 'findUser', }) } }
Stack定義以外のファイルは以下です。
#!/usr/bin/env node import 'source-map-support/register' import * as cdk from 'aws-cdk-lib' import { AppSyncStack } from '../lib/appsync-stack' const app = new cdk.App() new AppSyncStack(app, 'AppSyncStack', { env: { region: 'ap-northeast-1' }, })
import { AppSyncResolverEvent } from 'aws-lambda' export const handler = async (event: AppSyncResolverEvent<{}, {}>) => { console.log(JSON.stringify({ event })) return { id: '1', name: 'John Doe', } }
type User { id: String! name: String! } type Query { findUser: User! }
ログをCloudWatch Logsに出力する
まずはAppSyncログをCloudWatch Logsに出力する設定を追加します。
// ログ出力のためにAppSyncにアタッチするIAM Role作成 const apiLogRole = new iam.Role(this, 'apiLogRole', { assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), path: '/service-role/', }) apiLogRole.attachInlinePolicy( new iam.Policy(this, 'apiLogRolePolicy', { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents', ], resources: [`arn:aws:logs:${this.region}:${this.account}:*`], }), ], }) ) // AppSyncAPIを作成 const api = new appsync.GraphqlApi(this, 'myAppsyncApi', { name: 'myAppsyncApi', schema: appsync.Schema.fromAsset('./schema.graphql'), logConfig: { role: apiLogRole, fieldLogLevel: appsync.FieldLogLevel.ERROR, }, authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY, }, }, })
ログ出力用のIAMロールを作成し、 logConfig
にロールとエラーレベルを渡すことで設定できました。
AWS WAFの設定と紐付け
続いてAWS WAFのWebACLを作成し、それをAppSyncに紐付けます。
[AWS CDK] WAF v2のAWSマネージドルールをCloudFrontに適用する | DevelopersIO
こちらを参考にして
- AWSManagedRulesCommonRuleSet
- AWSManagedRulesAdminProtectionRuleSet
- AWSManagedRulesKnownBadInputsRuleSet
- AWSManagedRulesAmazonIpReputationList
- AWSManagedRulesAnonymousIpList
をルールとして設定しました。
// AppSync用のWebACLを作成 const apiWebAcl = new wafV2.CfnWebACL(this, 'wafV2WebAcl', { defaultAction: { allow: {} }, // 'CLOUDFRONT' or 'REGIONAL' // AppSyncは'REGIONAL'を選択 scope: 'REGIONAL', visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'wafV2WebAclMetric', }, rules: [ { name: 'AWSManagedRulesCommonRuleSet', priority: 1, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesCommonRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesCommonRuleSet', }, }, { name: 'AWSManagedRulesAdminProtectionRuleSet', priority: 2, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAdminProtectionRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAdminProtectionRuleSet', }, }, { name: 'AWSManagedRulesKnownBadInputsRuleSet', priority: 3, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesKnownBadInputsRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesKnownBadInputsRuleSet', }, }, { name: 'AWSManagedRulesAmazonIpReputationList', priority: 4, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAmazonIpReputationList', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAmazonIpReputationList', }, }, { name: 'AWSManagedRulesAnonymousIpList', priority: 5, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAnonymousIpList', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAnonymousIpList', }, }, ], }) // AppSyncとWebACLを紐付ける const webAclAssociation = new wafV2.CfnWebACLAssociation(this, 'webAclAssociation', { resourceArn: api.arn, webAclArn: apiWebAcl.attrArn, }) // 本リソースはAppSyncとWebACLより後に設定されなければならないため、DependsOnに両者を設定 webAclAssociation.addDependsOn(apiWebAcl) webAclAssociation.addDependsOn(api.node.defaultChild as cdk.CfnResource)
X-Ray設定
最後にX-Rayを有効化します。これは xrayEnabled: true
を指定するだけで実現できます。
// AppSyncAPIを作成 const api = new appsync.GraphqlApi(this, 'myAppsyncApi', { name: 'myAppsyncApi', schema: appsync.Schema.fromAsset('./schema.graphql'), logConfig: { role: apiLogRole, fieldLogLevel: appsync.FieldLogLevel.ERROR, }, authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY, }, }, xrayEnabled: true, })
完成コード
import { Construct } from 'constructs' import * as cdk from 'aws-cdk-lib' import * as appsync from '@aws-cdk/aws-appsync-alpha' import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs' import * as logs from 'aws-cdk-lib/aws-logs' import * as iam from 'aws-cdk-lib/aws-iam' import * as wafV2 from 'aws-cdk-lib/aws-wafv2' export class AppSyncStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props) // ログ出力のためにAppSyncにアタッチするIAM Role作成 const apiLogRole = new iam.Role(this, 'apiLogRole', { assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), path: '/service-role/', }) apiLogRole.attachInlinePolicy( new iam.Policy(this, 'apiLogRolePolicy', { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents', ], resources: [`arn:aws:logs:${this.region}:${this.account}:*`], }), ], }) ) // AppSyncAPIを作成 const api = new appsync.GraphqlApi(this, 'myAppsyncApi', { name: 'myAppsyncApi', schema: appsync.Schema.fromAsset('./schema.graphql'), logConfig: { role: apiLogRole, fieldLogLevel: appsync.FieldLogLevel.ERROR, }, authorizationConfig: { defaultAuthorization: { authorizationType: appsync.AuthorizationType.API_KEY, }, }, xrayEnabled: true, }) // AppSync用のCloudWatch LogGroup作成 new logs.LogGroup(this, 'apiLogGroup', { logGroupName: `/aws/appsync/apis/${api.apiId}`, }) // AppSync用のWebACLを作成 const apiWebAcl = new wafV2.CfnWebACL(this, 'wafV2WebAcl', { defaultAction: { allow: {} }, // 'CLOUDFRONT' or 'REGIONAL' // AppSyncは'REGIONAL'を選択 scope: 'REGIONAL', visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'wafV2WebAclMetric', }, rules: [ { name: 'AWSManagedRulesCommonRuleSet', priority: 1, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesCommonRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesCommonRuleSet', }, }, { name: 'AWSManagedRulesAdminProtectionRuleSet', priority: 2, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAdminProtectionRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAdminProtectionRuleSet', }, }, { name: 'AWSManagedRulesKnownBadInputsRuleSet', priority: 3, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesKnownBadInputsRuleSet', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesKnownBadInputsRuleSet', }, }, { name: 'AWSManagedRulesAmazonIpReputationList', priority: 4, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAmazonIpReputationList', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAmazonIpReputationList', }, }, { name: 'AWSManagedRulesAnonymousIpList', priority: 5, statement: { managedRuleGroupStatement: { vendorName: 'AWS', name: 'AWSManagedRulesAnonymousIpList', }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: 'AWSManagedRulesAnonymousIpList', }, }, ], }) // AppSyncとWebACLを紐付ける const webAclAssociation = new wafV2.CfnWebACLAssociation(this, 'webAclAssociation', { resourceArn: api.arn, webAclArn: apiWebAcl.attrArn, }) // 本リソースはAppSyncとWebACLより後に設定されなければならないため、DependsOnに両者を設定 webAclAssociation.addDependsOn(apiWebAcl) webAclAssociation.addDependsOn(api.node.defaultChild as cdk.CfnResource) // Lambda関数を作成 const findUserFn = new lambdaNodejs.NodejsFunction(this, 'findUserFn', { entry: 'lambda-handler/find-user-handler.ts', }) // Lambda関数をDataSourceとしてAppSyncAPIと紐付ける const findUserDs = api.addLambdaDataSource('findUserDs', findUserFn) // schema.graphqlで定義した中のどの操作とマッピングするかを指定 findUserDs.createResolver({ typeName: 'Query', fieldName: 'findUser', }) } }
確認
npx cdk deploy
でデプロイし、作成されたAppSyncリソースの設定をマネジメントコンソールで開くと、
このようにひと通りの設定がオンになっていることを確認できました。